package lib

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"time"
)

type ServiceError struct {
	Status bool   `json:"status"`
	Error  string `json:"error"`
}

type ServiceResult struct {
	Status  bool   `json:"status"`
	Running bool   `json:"running"`
	Queued  bool   `json:"queued"`
	Error   string `json:"error"`
}

type SVNItemResult struct {
	Status bool      `json:"status"`
	Items  []SVNItem `json:"items"`
}

type TaskResult struct {
	Status  bool   `json:"status"`
	Task_id string `json:"task_id"`
}

type HTTPService struct {
	deploy                     *Deploy
	web_root                   string
	start_queue, running_queue chan int64
	task                       map[int64]func() error
	taskResult                 map[int64]*ServiceResult
}

var Max_Start_Task_Count = 1000

func NewHTTP(c *Config) *HTTPService {
	web_root_path := filepath.Join(filepath.Dir(os.Args[0]), "web")
	if _, err := os.Stat(web_root_path); err != nil && os.IsNotExist(err) {
		os.MkdirAll(web_root_path, 0777)
	}
	temp_path := filepath.Join(web_root_path, "temp")
	if _, err1 := os.Stat(temp_path); err1 != nil && os.IsNotExist(err1) {
		os.MkdirAll(temp_path, 0777)
	}

	return &HTTPService{
		deploy:        NewDeploy(c, temp_path),
		web_root:      web_root_path,
		start_queue:   make(chan int64, 100),
		running_queue: make(chan int64, 100),
		task:          make(map[int64]func() error),
		taskResult:    make(map[int64]*ServiceResult),
	}
}

func (s *HTTPService) Start() {
	r := mux.NewRouter()

	r.HandleFunc("/", s.RedirectSample)
	r.HandleFunc("/add", s.Add)
	r.HandleFunc("/edit/{name}", s.Edit)
	r.HandleFunc("/list", s.List)
	r.HandleFunc("/deploy/{name}", s.Deploy)
	r.HandleFunc("/task/{id}", s.GetTask)
	r.PathPrefix("/sample/").Handler(http.StripPrefix("/sample/",
		http.FileServer(http.Dir(fmt.Sprintf("%s/sample", s.web_root)))))
	r.NotFoundHandler = http.HandlerFunc(s.NotFoundHandle)

	go s.PullQueue()
	InfoLogger.Println("http service starting")
	InfoLogger.Printf("Please open http://%s\n", s.deploy.config.Listen)
	http.ListenAndServe(s.deploy.config.Listen, r)
}

func (s *HTTPService) PullQueue() {

	for {
		task_id, status := <-s.start_queue
		if status {
			s.running_queue <- task_id
			go func() {
				task_func, exists := s.task[task_id]
				if exists {
					s.taskResult[task_id] = &ServiceResult{Status: true, Running: true}
					InfoLogger.Println("task:", task_id, "running!")
					err := task_func()
					if err != nil {
						s.taskResult[task_id] = &ServiceResult{Status: false, Error: err.Error()}
					} else {
						s.taskResult[task_id] = &ServiceResult{Status: true}
					}
					//result will been cleared after 2 hours
					time.AfterFunc(time.Hour*2, func() {
						delete(s.taskResult, task_id)
					})
					InfoLogger.Println("get task:", task_id, " result:", s.taskResult[task_id])
					delete(s.task, task_id)
				}
				<-s.running_queue
			}()

		}
	}
}

func (s *HTTPService) getTaskID(fun func() error) (status bool, task_id int64) {
	if Max_Start_Task_Count >= len(s.start_queue) {
		status = true
		task_id = time.Now().UnixNano()
		s.task[task_id] = fun
		s.start_queue <- task_id
		s.taskResult[task_id] = &ServiceResult{Queued: true, Status: true}
		InfoLogger.Println("task:", task_id, " Queued")
		return
	}

	status = false
	task_id = 0
	return
}

func (s *HTTPService) GetIntVar(key string, _default int, request *http.Request) (result int) {
	vars := mux.Vars(request)
	value, status := vars[key]
	if !status {
		return _default
	}
	out, err := strconv.ParseInt(value, 10, 0)
	if err != nil {
		return _default
	}
	return int(out)
}

func (s *HTTPService) GetStringVar(key string, _default string, request *http.Request) (result string) {
	vars := mux.Vars(request)
	value, status := vars[key]
	if !status {
		return _default
	}
	return value
}

func (s *HTTPService) GetBoolVar(key string, _default bool, request *http.Request) (result bool) {
	vars := mux.Vars(request)
	value, status := vars[key]
	if !status {
		return _default
	}
	out, err := strconv.ParseBool(value)
	if err != nil {
		return _default
	}
	return out
}

func (s *HTTPService) ResponseJSON(source interface{}, writer http.ResponseWriter) {
	json_str, err := json.Marshal(source)
	if err != nil {
		s.ResponseError(err, writer)
	}
	writer.Header().Set("Content-Type", "application/json")
	fmt.Fprintf(writer, string(json_str))
}

func (s *HTTPService) NotFoundHandle(writer http.ResponseWriter, request *http.Request) {
	server_error := &ServiceError{Error: "handle not found!", Status: false}
	json_str, _ := json.Marshal(server_error)
	http.Error(writer, string(json_str), 404)
}

func (s *HTTPService) ResponseError(err error, writer http.ResponseWriter) {
	server_error := &ServiceError{Error: err.Error(), Status: false}
	json_str, _ := json.Marshal(server_error)
	http.Error(writer, string(json_str), 200)
}

func (s *HTTPService) GetSVNItem(request *http.Request) SVNItem {
	return SVNItem{
		Name:      request.FormValue("name"),
		URL:       request.FormValue("url"),
		Username:  request.FormValue("user"),
		Password:  request.FormValue("pass"),
		LocalPath: request.FormValue("remote"),
		SSH: SSHItem{
			Host:       request.FormValue("host"),
			Username:   request.FormValue("ssh_user"),
			Password:   request.FormValue("ssh_pass"),
			PrivateKey: request.FormValue("ssh_key"),
		},
	}
}

func (s *HTTPService) RedirectSample(writer http.ResponseWriter, request *http.Request) {
	http.Redirect(writer, request, "/sample/index.html", 301)
}

func (s *HTTPService) GetTask(writer http.ResponseWriter, request *http.Request) {
	task_id := s.GetIntVar("id", 0, request)
	result, exists := s.taskResult[int64(task_id)]
	if !exists {
		s.ResponseError(errors.New("task not exists"), writer)
		return
	}
	if !result.Status {
		s.ResponseError(errors.New(result.Error), writer)
		return
	}
	s.ResponseJSON(result, writer)
}

func (s *HTTPService) List(writer http.ResponseWriter, request *http.Request) {

	s.ResponseJSON(&SVNItemResult{Status: true, Items: s.deploy.List()}, writer)
	return
}

func (s *HTTPService) Add(writer http.ResponseWriter, request *http.Request) {
	item := s.GetSVNItem(request)
	err := s.deploy.AddItem(item)
	if err != nil {
		s.ResponseError(err, writer)
		return
	}

	s.ResponseJSON(&ServiceResult{Status: true}, writer)
}

func (s *HTTPService) Edit(writer http.ResponseWriter, request *http.Request) {
	name := s.GetStringVar("name", "", request)
	item := s.GetSVNItem(request)
	item.Name = name
	err := s.deploy.EditByName(name, item)
	if err != nil {
		s.ResponseError(err, writer)
		return
	}
	s.ResponseJSON(&ServiceResult{Status: true}, writer)
}

func (s *HTTPService) Deploy(writer http.ResponseWriter, request *http.Request) {
	name := s.GetStringVar("name", "", request)
	success, task_id := s.getTaskID(func() (err error) {
		err = s.deploy.DeployItem(name)
		return
	})

	if !success {
		s.ResponseError(errors.New("max started task!"), writer)
		return
	}
	s.ResponseJSON(&TaskResult{Status: true, Task_id: fmt.Sprintf("%d", task_id)}, writer)
	return
}
